// (c) Copyright 2013-2014 Stephen Gold <sgold@sonic.net>
// Distributed under the terms of the GNU General Public License

/*
 This file is part of the Bats Game.

 The Bats Game is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by the
 Free Software Foundation, either version 3 of the License, or (at your
 option) any later version.

 The Bats Game is distributed in the hope that it will be useful, but
 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 for more details.

 You should have received a copy of the GNU General Public License
 along with the Bats Game.  If not, see <http://www.gnu.org/licenses/>.
 */
package bats.scene;

import bats.indicator.AxesIndicator;
import bats.indicator.Cursor3DIndicator;
import bats.indicator.PickIndicator;
import bats.indicator.TargetIndicator;
import bats.indicator.ViewpointIndicator;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import java.util.logging.Logger;
import jme3utilities.MySpatial;

/**
 * A node control to manage the unshaded indicators in a scene, including the 3D
 * cursor, the coordinate axes, and all pick indicators.
 *
 * @author Stephen Gold <sgold@sonic.net>
 */
public class IndicatorsNode
        extends NodeControl {
    // *************************************************************************
    // constants

    /**
     * message logger for this class
     */
    final private static Logger logger =
            Logger.getLogger(IndicatorsNode.class.getName());
    /**
     * name for the node in the scene graph
     */
    final private static String nodeName = "indicators";
    // *************************************************************************
    // fields
    /**
     * visible axes in the scene: set in constructor
     */
    final private AxesIndicator axes;
    /**
     * 3D cursor in the scene: set in constructor
     */
    final private Cursor3DIndicator cursor3D;
    // *************************************************************************
    // constructors

    /**
     * Instantiate the enabled control for the specified scene. After
     * instantiation, the first method invoked should be initialize().
     *
     * @param scene where the indicators will reside (not null)
     * @param worldRadius distance from origin to nearest map edge (in meters,
     * &gt;0)
     */
    public IndicatorsNode(SceneNode scene, float worldRadius) {
        super(scene);
        assert worldRadius > 0f : worldRadius;

        axes = new AxesIndicator(scene, worldRadius);
        cursor3D = new Cursor3DIndicator(scene);

        assert isEnabled();
    }
    // *************************************************************************
    // new methods exposed

    /**
     * Delete the indicators (if any) controlled by a specified spatial.
     *
     * @param controllingSpatial which spatial (not null)
     */
    public void deleteIndicator(Spatial controllingSpatial) {
        assert controllingSpatial != null;

        for (;;) {
            Spatial indicator = findIndicator(controllingSpatial);
            if (indicator == null) {
                return;
            }
            scene.deleteSpatial(indicator);
        }
    }

    /**
     * Access the visible coordinate axes.
     *
     * @return the pre-existing instance (not null)
     */
    public AxesIndicator getAxes() {
        assert axes != null;
        return axes;
    }

    /**
     * Access the 3D cursor.
     *
     * @return the pre-existing instance (not null)
     */
    public Cursor3DIndicator getCursor3D() {
        assert cursor3D != null;
        return cursor3D;
    }

    /**
     * Initialize this control.
     */
    public void initialize() {
        assert spatial == null : spatial;

        Node node = new Node(nodeName);
        node.addControl(this);
        node.setCullHint(Spatial.CullHint.Dynamic);
        /*
         * Attach the node to the scene's top node.
         */
        scene.attachChild(spatial);

        axes.initialize();
        cursor3D.initialize();

        assert spatial != null;
        assert !MySpatial.isOrphan(spatial);
    }

    /**
     * Process an action which has just begun.
     *
     * @param words action string split into an array of words (not null)
     * @return true if the action was handled, false if it wasn't handled
     */
    public boolean processAction(String[] words) {
        String verb = words[0];
        switch (verb) {
            case "toggle":
                assert words.length > 1;
                String what = words[1];
                switch (what) {
                    case "3DCursor":
                        /*
                         * Toggle visibility of the 3D cursor.
                         */
                        cursor3D.toggleEnabled();
                        return true;
                    case "axes":
                        /*
                         * Toggle visibility of coordinate axes.
                         */
                        axes.toggleEnabled();
                        return true;
                }
                break;
        }

        return false; // not handled
    }

    /**
     * Alter the visibility of the 3D cursor.
     *
     * @param newState true to reveal the cursor, false to hide it
     */
    public void setCursor(boolean newState) {
        boolean state = cursor3D.isEnabled();
        Spatial geometry = cursor3D.getSpatial();
        if (newState && !state) {
            reveal(geometry);
        } else if (!newState && state) {
            detach(geometry);
        }
        cursor3D.setEnabled(newState);
    }

    /**
     * Attach an orphan indicator to the scene graph.
     *
     * @param spatial which indicator to attach (not null)
     */
    public void reveal(Spatial spatial) {
        assert spatial != null;
        assert MySpatial.isOrphan(spatial) : spatial.getParent();
        attach(spatial);
    }
    // *************************************************************************
    // AbstractControl methods

    /**
     * Update the indicators.
     *
     * @param tpf elapsed time since the previous update (in seconds, &ge;0)
     */
    @Override
    public void update(float tpf) {
        /*
         * Update the 3D cursor.
         */
        cursor3D.update(tpf);
        /*
         * Update the visible pick indicators.
         */
        for (Spatial child : getChildren()) {
            PickIndicator control = child.getControl(PickIndicator.class);
            if (control != null) {
                control.update(tpf);
            }
        }
    }
    // *************************************************************************
    // private methods

    /**
     * Find the first indicator spatial controlled by the specified spatial.
     *
     * @param controllingSpatial which controlling spatial (not null)
     * @return the pre-existing spatial (or null if there's none)
     */
    private Spatial findIndicator(Spatial controllingSpatial) {
        assert controllingSpatial != null;

        for (Spatial child : getChildren()) {
            PickIndicator pick =
                    child.getControl(PickIndicator.class);
            if (pick != null
                    && pick.getControllingSpatial() == controllingSpatial) {
                return child;
            }

            TargetIndicator target =
                    child.getControl(TargetIndicator.class);
            if (target != null
                    && target.getControllingSpatial() == controllingSpatial) {
                return child;
            }

            ViewpointIndicator viewpoint =
                    child.getControl(ViewpointIndicator.class);
            if (viewpoint != null && viewpoint.getControllingSpatial()
                    == controllingSpatial) {
                return child;
            }
        }
        return null;
    }
}